/**
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership.  The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with the License.  You may obtain
 * a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package org.apache.zookeeper.server;

import java.nio.ByteBuffer;
import java.util.List;

import org.apache.jute.Record;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs.OpCode;
import org.apache.zookeeper.common.Time;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.txn.TxnHeader;

/**
 *
 * This is the structure that represents a request moving through a chain of RequestProcessors.
 * 这个结构表示一个请求，在多个 RequestProcessor 链中经过。
 *
 * There are various pieces of information that is tacked onto the request as it is processed.
 * 在处理请求时，有各种信息被钉在请求上。
 */
public class Request {
    private static final Logger LOG = LoggerFactory.getLogger(Request.class);

    public final static Request requestOfDeath = new Request(null, 0, 0, 0, null, null);

    public final long sessionId;

    /**
     * client xid   {@link org.apache.zookeeper.ClientCnxn#xid}
     */
    public final int cxid;

    /**
     * @see OpCode
     */
    public final int type;

    /**
     * 请求数据序列化的byteBuffer
     */
    public final ByteBuffer request;

    /**
     * client 与 server之间网络socket
     */
    public final ServerCnxn cnxn;

    public final List<Id> authInfo;
    /**
     * 请求创建的时间
     * 用于计算请求创建到请求处理完毕之间的事件
     *
     * @see ServerStats#updateLatency
     */
    public final long createTime = Time.currentElapsedTime();

    /**
     * 事务请求头
     */
    public TxnHeader hdr;

    /**
     * 事务对象。
     */
    public Record txn;

    /**
     *
     */
    public long zxid = -1;
    private Object owner;
    private KeeperException e;

    /**
     * @param cnxn
     * @param sessionId
     * @param cxid client xid {@link org.apache.zookeeper.ClientCnxn#xid}
     * @param type
     * @param bb
     */
    public Request(ServerCnxn cnxn, long sessionId, int cxid, int type, ByteBuffer bb, List<Id> authInfo) {
        this.cnxn = cnxn;
        this.sessionId = sessionId;
        this.cxid = cxid;
        this.type = type;
        this.request = bb;
        this.authInfo = authInfo;
    }

    /**
     * is the packet type a valid packet in zookeeper
     *
     * @param type the type of the packet
     * @return true if a valid packet, false if not
     */
    static boolean isValid(int type) {
        // make sure this is always synchronized with Zoodefs!!
        switch (type) {
            case OpCode.notification: return false;
            case OpCode.create:
            case OpCode.delete:
            case OpCode.createSession:
            case OpCode.exists:
            case OpCode.getData:
            case OpCode.check:
            case OpCode.multi:
            case OpCode.setData:
            case OpCode.sync:
            case OpCode.getACL:
            case OpCode.setACL:
            case OpCode.getChildren:
            case OpCode.getChildren2:
            case OpCode.ping:
            case OpCode.closeSession:
            case OpCode.setWatches: return true;
            default: return false;
        }
    }

    /**
     * 根据请求类型判断是否需要多节点进行处理事务
     * 读操作都不需要，写操作都需要
     * @param type
     * @return
     */
    static boolean isQuorum(int type) {
        switch (type) {
            case OpCode.exists:
            case OpCode.getACL:
            case OpCode.getChildren:
            case OpCode.getChildren2:
            case OpCode.getData: return false;
            case OpCode.error:
            case OpCode.closeSession:
            case OpCode.create:
            case OpCode.createSession:
            case OpCode.delete:
            case OpCode.setACL:
            case OpCode.setData:
            case OpCode.check:
            case OpCode.multi: return true;
            default: return false;
        }
    }

    static String op2String(int requestType) {
        switch (requestType) {
            case OpCode.notification: return "notification";
            case OpCode.create: return "create";
            case OpCode.setWatches: return "setWatches";
            case OpCode.delete: return "delete";
            case OpCode.exists: return "exists";
            case OpCode.getData: return "getData";
            case OpCode.check: return "check";
            case OpCode.multi: return "multi";
            case OpCode.setData: return "setData";
            case OpCode.sync: return "sync:";
            case OpCode.getACL: return "getACL";
            case OpCode.setACL: return "setACL";
            case OpCode.getChildren: return "getChildren";
            case OpCode.getChildren2: return "getChildren2";
            case OpCode.ping: return "ping";
            case OpCode.createSession: return "createSession";
            case OpCode.closeSession: return "closeSession";
            case OpCode.error: return "error";
            default: return "unknown " + requestType;
        }
    }

    public Object getOwner() {
        return owner;
    }

    public void setOwner(Object owner) {
        this.owner = owner;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("sessionid:0x").append(Long.toHexString(sessionId)).append(" type:").append(op2String(type)).append(" cxid:0x").append(Long.toHexString(cxid)).append(" zxid:0x").append(Long.toHexString(hdr == null ? -2 : hdr.getZxid())).append(" txntype:").append(hdr == null ? "unknown" : "" + hdr.getType());

        // best effort to print the path assoc with this request
        String path = "n/a";
        if (type != OpCode.createSession
                && type != OpCode.setWatches
                && type != OpCode.closeSession
                && request != null
                && request.remaining() >= 4) {
            try {
                // make sure we don't mess with request itself
                ByteBuffer rbuf = request.asReadOnlyBuffer();
                rbuf.clear();
                int pathLen = rbuf.getInt();
                // sanity check
                if (pathLen >= 0
                        && pathLen < 4096
                        && rbuf.remaining() >= pathLen) {
                    byte b[] = new byte[pathLen];
                    rbuf.get(b);
                    path = new String(b);
                }
            } catch (Exception e) {
                // ignore - can't find the path, will output "n/a" instead
            }
        }
        sb.append(" reqpath:").append(path);

        return sb.toString();
    }

    public KeeperException getException() {
        return e;
    }

    public void setException(KeeperException e) {
        this.e = e;
    }
}
